package org.batatasframework.support.factory;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.batatasframework.support.database.TableInfo;
import org.batatasframework.support.database.jdbc.ConnectionAccessor;
import org.batatasframework.support.database.metadatas.FieldMetadata;
import org.batatasframework.support.database.metadatas.TableMetadata;
import org.batatasframework.support.definition.ConditionDefinition;
import org.batatasframework.support.definition.FieldDefinition;
import org.batatasframework.support.definition.FieldDefinitionSourcable;
import org.batatasframework.support.definition.JoinDefinition;
import org.batatasframework.support.definition.QueryDefinition;
import org.batatasframework.support.definition.TableDefinition;
import org.batatasframework.support.definition.TableDefinitionBuilder;
import org.batatasframework.support.definition.TableDefinitionGenerator;
import org.batatasframework.support.definition.TableDefinitionResource;
import org.batatasframework.support.definition.impl.TableDefinitionImpl;
import org.batatasframework.support.definition.impl.TableDefinitionImpl.ConcatFieldDefintion;
import org.batatasframework.support.definition.impl.TableDefinitionImpl.DefaultFieldDefinition;
import org.batatasframework.support.definition.impl.TableDefinitionImpl.FormatFieldDefinition;
import org.batatasframework.support.editable.EditablePlatform;
import org.batatasframework.support.editable.internal.StandardEditablePlatform;
import org.batatasframework.support.loader.DatabasePropertiesLoader;
import org.batatasframework.support.searchable.SearchablePlatform;
import org.batatasframework.support.searchable.internal.StandardSearchablePlatform;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.util.Assert;

import cn.bonoon.kernel.IdGenerator;


public class DefaultListableTableFactory extends DatabasePropertiesLoader implements TableDefinitionFactory, ApplicationContextAware, EmbeddedValueResolverAware, InitializingBean{
	
	/** 如果存在两个同名的表的定义，是否允许后面的覆盖前面的定义。 */
	private boolean allowTableDefinitionOverriding = true;
	
	/** 解析后的表的定义 */
	private final Map<String, TableDefinition> tableDefinitionMapped = new ConcurrentHashMap<>();
	
	private StandardSearchablePlatform searchablePlatform;
	
	private StandardEditablePlatform editablePlatform;
	
	private final IdGenerator idGenerator = new IdGenerator("F_");
	
	public String newKey(){
		return idGenerator.generator();
	}
	
	private TableDefinitionGenerator[] generators;
	
	@Override
	public void afterPropertiesSet() throws Exception {
		
		if(null == searchablePlatform){ searchablePlatform = createSearchablePlatform(); }
		if(null == editablePlatform){ editablePlatform = createEditablePlatform(); }
		
		super.afterPropertiesSet();
		
		if(null != generators){
			for(TableDefinitionGenerator generator : generators){
				
			}
		}
	}
	
	/** 应该通过spring的配置文件注入进来 */
	public void setGenerators(TableDefinitionGenerator[] generators) {
		this.generators = generators;
	}
	
	protected StandardSearchablePlatform createSearchablePlatform(){
		return new StandardSearchablePlatform(this, databaseMetadataSources);
	}
	
	protected StandardEditablePlatform createEditablePlatform(){
		return new StandardEditablePlatform(this, databaseMetadataSources);
	}
	
	@Override
	public void registerTableDefinition(String name, TableDefinition tableDefinition) throws Exception {
		Assert.notNull(tableDefinition, "被注册的对象不能为null！");
		
		Assert.isTrue(
				tableDefinitionMapped.put(name, tableDefinition) == null || allowTableDefinitionOverriding, 
				"已经存在定义，名为：" + name
			);
	}
	
	@Override
	public TableDefinition getTableDefinition(String name) {
		return tableDefinitionMapped.get(name);
	}

	@Override
	public SearchablePlatform getSearchablePlatform() {
		return searchablePlatform;
	}

	@Override
	public EditablePlatform getEditablePlatform() {
		return editablePlatform;
	}

	@Override
	public TableDefinitionBuilder buildSearchableContextBuilder(
			String schemaName, String catalogName, String tableName) throws Exception {
		return new InternalSearchableContextBuilder(schemaName, catalogName, tableName);
	}
	
	@Override
	public TableDefinitionBuilder buildSearchableContextBuilder(TableMetadata sourceTableMetadata) throws Exception {
		return new InternalSearchableContextBuilder(sourceTableMetadata);
	}
	
	private TableDefinitionImpl createStandardSearchableContext(TableMetadata tableMetadata){
		return new TableDefinitionImpl(this, tableMetadata);
	}
	
	public class InternalSearchableContextBuilder implements TableDefinitionBuilder{
		
		private final TableDefinitionImpl internalTableDefinition;
		/** 这个对象需要被安全关闭 */
		private final ConnectionAccessor connectionAccessor;
		private final TableMetadata sourceTableMetadata;
		
		public InternalSearchableContextBuilder(String schemaName, String catalogName, String tableName) throws Exception {
			
			TableInfo tableInfo = new TableInfo(schemaName, catalogName, tableName);
			this.connectionAccessor = buildDatabaseAccessor();
			this.sourceTableMetadata = databaseMetadataSources.loadTableMetadata(connectionAccessor, tableInfo);
			this.internalTableDefinition = createStandardSearchableContext(sourceTableMetadata);
		}
		
		public InternalSearchableContextBuilder(TableMetadata sourceTableMetadata) throws Exception{
			this.connectionAccessor = buildDatabaseAccessor();
			this.sourceTableMetadata = sourceTableMetadata;
			this.internalTableDefinition = createStandardSearchableContext(sourceTableMetadata);
		}

		@Override
		public void close() throws Exception {
			connectionAccessor.close();
		}

		@Override
		public TableDefinition build() throws Exception {
			//在这里处理哪些是分组的，哪些是返回的结果集，哪些可以是查询条件。
			internalTableDefinition.flush();
			
			return internalTableDefinition;
		}
		
		@Override
		public void query(String key, QueryDefinition queryDefinition) {
			internalTableDefinition.query(key, queryDefinition);
		}
		
		@Override
		public void sort(String... fields) {
			for(String s:fields){
				FieldMetadata fm=internalTableDefinition.get(s);
				internalTableDefinition.sort(internalTableDefinition, fm);
			}
		}
		
		@Override
		public void sort(JoinDefinition joinTable, String... fields) {
			for(String s:fields){
				FieldMetadata fm = joinTable.get(s);
				internalTableDefinition.sort(joinTable, fm);
			}
		}

		//---------------------------------joins
		@Override
		public JoinDefinition join(String schemaName, String catalogName, String tableName,boolean must) throws Exception {
			return _join(internalTableDefinition, new TableInfo(schemaName, catalogName, tableName),must);
		}

		@Override
		public JoinDefinition join(String tableName,boolean must) throws Exception {
			return _join(internalTableDefinition, new TableInfo(sourceTableMetadata.getTableInfo(), tableName),must);
		}
		
		@Override
		public JoinDefinition join(TableDefinitionResource tableDefinition, String tableName,boolean must) throws Exception {

			return _join(tableDefinition, new TableInfo(tableDefinition.getTableMetadata().getTableInfo(), tableName),must);
		}

		@Override
		public JoinDefinition join(TableInfo tableInfo,boolean must) throws Exception {
			return _join(internalTableDefinition, tableInfo,must);
		}

		private JoinDefinition _join(TableDefinitionResource tableDefinition, TableInfo tableInfo,boolean must) throws Exception {
			return internalTableDefinition.join(tableDefinition, databaseMetadataSources.loadTableMetadata(connectionAccessor, tableInfo),must);
		}
		
		@Override
		public TableDefinition sourceTableDefinition() {
			return internalTableDefinition;
		}
		
		//------------------------------------fields
		@Override
		public DefaultFieldDefinition field(String fieldName) throws Exception {
			return _field(internalTableDefinition, fieldName);
		}

		@Override
		public DefaultFieldDefinition field(String title, String fieldName) throws Exception {
			DefaultFieldDefinition fieldDefinition = _field(internalTableDefinition, fieldName);
			fieldDefinition.setTitle(title);
			return fieldDefinition;
		}

		@Override
		public DefaultFieldDefinition field(JoinDefinition joinTable, String fieldName) throws Exception {
			return _field(joinTable, fieldName);
		}
		
		@Override
		public DefaultFieldDefinition field(JoinDefinition joinTable, String title, String fieldName) throws Exception {
			
			DefaultFieldDefinition fieldDefinition = _field(joinTable, fieldName);
			fieldDefinition.setTitle(title);
			
			return fieldDefinition;
		}
		
		private DefaultFieldDefinition _field(TableDefinitionResource tableDefinition, String fieldName) throws Exception {
			
			if(null == tableDefinition) tableDefinition = internalTableDefinition;
			
//			TableMetadata tableMetadata = tableDefinition.getSourceTableMetadata();
			FieldMetadata sourceFieldMetadata = tableDefinition.get(fieldName);
			return internalTableDefinition.field(newKey(), tableDefinition, sourceFieldMetadata);
		}

		@Override
		public ConcatFieldDefintion concat(String title) throws Exception {
			return _concat(title);
		}

		@Override
		public ConcatFieldDefintion concat(String title, FieldDefinitionSourcable... fieldSources) throws Exception {
			ConcatFieldDefintion fieldDefintion = _concat(title);
			fieldDefintion.append(fieldSources);
			return fieldDefintion;
		}

		@Override
		public ConcatFieldDefintion concat(String title, String... fieldNames) throws Exception {
			ConcatFieldDefintion fieldDefintion = _concat(title);
			fieldDefintion.append(internalTableDefinition, fieldNames);
			return fieldDefintion;
		}

		@Override
		public ConcatFieldDefintion concat(String title,
				JoinDefinition joinTable, String... fieldNames)
				throws Exception {
			ConcatFieldDefintion fieldDefintion = _concat(title);
			fieldDefintion.append(joinTable, fieldNames);
			return fieldDefintion;
		}
		
		@Override
		public ConcatFieldDefintion concat(String title, String fieldName,
				JoinDefinition joinTable, String... fieldNames)
				throws Exception {
			ConcatFieldDefintion fieldDefintion = _concat(title);
			fieldDefintion.append(internalTableDefinition,fieldName);
			fieldDefintion.append(joinTable, fieldNames);
			return fieldDefintion;
		}
		
		private ConcatFieldDefintion _concat(String title) throws Exception{
			ConcatFieldDefintion fieldDefintion = internalTableDefinition.concat(newKey());
			fieldDefintion.setTitle(title);
			return fieldDefintion;
		}

		@Override
		public FormatFieldDefinition format(String title, String format) throws Exception {
			return _format(title, format);
		}

		@Override
		public FormatFieldDefinition format(String title, String format,
				FieldDefinitionSourcable... fieldSources) throws Exception {
			FormatFieldDefinition fieldDefintion = _format(title, format);
			fieldDefintion.append(fieldSources);
			return fieldDefintion;
		}

		@Override
		public FormatFieldDefinition format(String title, String format,
				String... fieldNames) throws Exception {
			FormatFieldDefinition fieldDefintion = _format(title, format);
			fieldDefintion.append(internalTableDefinition, fieldNames);
			return fieldDefintion;
		}

		@Override
		public FormatFieldDefinition format(String title, String format,
				JoinDefinition joinTable, String... fieldNames)
				throws Exception {
			FormatFieldDefinition fieldDefintion = _format(title, format);
			fieldDefintion.append(joinTable, fieldNames);
			return fieldDefintion;
		}
		
		private FormatFieldDefinition _format(String title, String format){
			FormatFieldDefinition fieldDefintion = internalTableDefinition.format(newKey(), format);
			fieldDefintion.setTitle(title);
			return fieldDefintion;
		}

		@Override
		public ConditionDefinition condition(String query) {
			return internalTableDefinition.query(internalTableDefinition, query, false);
		}

		@Override
		public ConditionDefinition condition(JoinDefinition joinTable, String query, boolean must) {
			return internalTableDefinition.query(joinTable, query, must);
		}

		@Override
		public void autoMatch() throws Exception {
			_autoMatch(Collections.<String>emptyList());
		}

		@Override
		public void autoMatch(String... fieldExcludes) throws Exception {
			_autoMatch(Arrays.asList(fieldExcludes));
		}
		
		private void _autoMatch(List<String> excludes){
			for(FieldMetadata fieldMetadata : sourceTableMetadata.getFieldMetadatas()){
				if(fieldMetadata.enabled() 
						&& !excludes.contains(fieldMetadata.getName()) 
						&& !internalTableDefinition.contains(fieldMetadata)){
					if(fieldMetadata instanceof FieldDefinition){
						internalTableDefinition.field(newKey(), (FieldDefinition)fieldMetadata);
					}else{
						internalTableDefinition.field(newKey(), internalTableDefinition, fieldMetadata);
					}
				}
			}
		}
	}

	@Override
	protected TableDefinitionRegistry getTableFactoryRegistry() {
		return this;
	}

}
